/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original NPAPI Plugin Code is
 * Aplix Corporation. anselm@aplixcorp.com
 * Portions created by the Initial Developer are Copyright (c) 2008-2009
 * the Initial Developer. All Rights Reserved.

 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#define STRICT
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <windowsx.h>
#include "LangUtil.h"

#if defined(XULRUNNER_SDK)
  #include <npapi.h>
  #include <npupp.h>
  #include <npruntime.h>
#elif defined(ANDROID)
  #undef HAVE_LONG_LONG
  #include <jni.h>
  #include <npapi.h>
  #include <npfunctions.h>
  #include <npruntime.h>
  #define OSCALL
  #define NPP_WRITE_TYPE (NPP_WriteProcPtr)
  #define NPStringText UTF8Characters
  #define NPStringLen  UTF8Length

  extern JNIEnv *pluginJniEnv;

  #undef STRINGN_TO_NPVARIANT
  #define STRINGN_TO_NPVARIANT(_val, _len, _v)                                \
  NP_BEGIN_MACRO                                                              \
    (_v).type = NPVariantType_String;                                         \
    NPString str = { _val, (uint32_t)(_len) };                                \
    (_v).value.stringValue = str;                                             \
  NP_END_MACRO

#elif defined(WEBKIT_DARWIN_SDK)

  #include <Webkit/npapi.h>
  #include <WebKit/npfunctions.h>
  #include <WebKit/npruntime.h>
  #define OSCALL

#elif defined(WEBKIT_WINMOBILE_SDK) /* WebKit SDK on Windows */

  #ifndef PLATFORM
    #define PLATFORM(x) defined(x)
  #endif
  #include <npfunctions.h>
  #ifndef OSCALL
    #define OSCALL WINAPI
  #endif

#endif

typedef int16_t int16;
typedef uint16_t uint16;
typedef uint32_t uint32;

static NPObject *       so       = NULL;
static NPNetscapeFuncs *npnfuncs = NULL;
static NPP              inst     = NULL;

static unsigned int g_hwnd = 0; // plugin's hosting window
static unsigned int p_hwnd = 0; // page container window for hosting window
static unsigned int b_hwnd = 0; // browser's top window
static DWORD g_thread = 0;

/* NPN */

static void logmsg(const char *msg)
{
#if defined(ANDROID)
  FILE *out = fopen("/sdcard/autolang.log", "a");
  if(out)
  {
    fputs(msg, out);
    fclose(out);
  }
#elif !defined(_WINDOWS)
  fputs(msg, stderr);
#else
  FILE *out = fopen("\\autolang.log", "a");
  if(out)
  {
    fputs(msg, out);
    fclose(out);
  }
#endif
}

static bool
hasMethod(NPObject* obj, NPIdentifier methodName)
{
  char *name = npnfuncs->utf8fromidentifier(methodName);

  if(!strcmp(name, "getlang"))
  {
    return true;
  }
  else
  if(!strcmp(name, "getlangname"))
  {
    return true;
  }
  else
  if(!strcmp(name, "setlang"))
  {
    return true;
  }

  return false;
}

static bool
invokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
#if defined(LOGMSG)
  logmsg("autolang: invokeDefault\n");
#endif
  result->type = NPVariantType_Int32;
  result->value.intValue = -1;
  return true;
}

static bool
invokeGetLanguage(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  result->type = NPVariantType_Int32;
  result->value.intValue = 0;
  if(argCount != 0) return false;

  result->value.intValue = getKeyboardLanguage(g_thread);
  return true;
}

static bool
invokeSetLanguage(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  result->type = NPVariantType_Int32;
  result->value.intValue = 0;
  if(argCount != 1)
  {
#if defined(LOGMSG)
    logmsg("argCount != 1\n");
#endif
    return false;
  }

  WORD w;

  if(args[0].type == NPVariantType_Int32)
  {
    w = NPVARIANT_TO_INT32(args[0]);
  }
  else
  if(args[0].type == NPVariantType_Double)
  {
    w = (WORD)NPVARIANT_TO_DOUBLE(args[0]);
  }
  else
  {
#if defined(LOGMSG)
    if(args[0].type == NPVariantType_Void)
    {
      logmsg("type is void\n");
    }
    if(args[0].type == NPVariantType_String)
    {
      logmsg("type is string\n");
    }
    if(args[0].type == NPVariantType_Object)
    {
      logmsg("type is object\n");
    }
    if(args[0].type == NPVariantType_Bool)
    {
      logmsg("type is bool\n");
    }
#endif
    return false;
  }

#if defined(LOGMSG)
  logmsg("setting ");
  char buffer[32];
  logmsg(ultoa((unsigned long)w, buffer, 10));
  logmsg("\n");
#endif  

  switchKeyboardLanguage(w, g_thread);

  result->value.intValue = 1;
  return true;
}

static void packNPStringResult(const char *data, NPVariant *result)
{
  int len = lstrlen(data);
  if(data != NULL)
  {
    result->type = NPVariantType_String;
    NPUTF8 *out = (NPUTF8 *)(*(npnfuncs->memalloc))(len + 1);

    if(out)
    {
      CopyMemory(out, data, len + 1);
      //STRINGZ_TO_NPVARIANT(out, *result);

      result->value.stringValue.UTF8Characters = out;
      result->value.stringValue.UTF8Length = len;
    }
#if defined(LOGMSG)
    else
    {
      logmsg("out of memory");
    }
#endif    

    GlobalFree((void *)data);
  }
}

static bool
invoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  char *name = npnfuncs->utf8fromidentifier(methodName);
  if(name)
  {
    if(!strcmp(name, "getlang"))
    {
      return invokeGetLanguage(obj, args, argCount, result);
    }
    else
    if(!strcmp(name, "getlangname"))
    {
      const char *data = getKeyboardLanguageName();
      packNPStringResult(data, result);
      return true;
    }
    else
    if(!strcmp(name, "setlang"))
    {
      return invokeSetLanguage(obj, args, argCount, result);
    }
  }
  // aim exception handling
  npnfuncs->setexception(obj, "exception during invocation");
  return false;
}

static bool
hasProperty(NPObject *obj, NPIdentifier propertyName)
{
  return false;
}

static bool
getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result)
{
  return false;
}

static NPClass npcRefObject = {
  NP_CLASS_STRUCT_VERSION,
  NULL,
  NULL,
  NULL,
  hasMethod,
  invoke,
  invokeDefault,
  hasProperty,
  getProperty,
  NULL,
  NULL,
};

static NPError
create(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char *argn[], char *argv[], NPSavedData *saved)
{
  inst = instance;
  return NPERR_NO_ERROR;
}

static NPError
destroy(NPP instance, NPSavedData **save)
{
  if(so)
    npnfuncs->releaseobject(so);
  so = NULL;
  return NPERR_NO_ERROR;
}

static NPError
getValue(NPP instance, NPPVariable variable, void *value)
{
  inst = instance;
  switch(variable)
  {
    default:
      return NPERR_GENERIC_ERROR;

    case NPPVpluginNameString:
      *((char **)value) = "LanguageKeepingTabs";
      break;

    case NPPVpluginDescriptionString:
      *((char **)value) = "<a href=\"http://code.google.com/p/language-keeping-tabs/\">LanguageKeepingTabs</a> plugin.";
      break;

    case NPPVpluginScriptableNPObject:
      if(!so)
        so = npnfuncs->createobject(instance, &npcRefObject);
      npnfuncs->retainobject(so);
      *(NPObject **)value = so;
      break;

#if defined(XULRUNNER_SDK)
    case NPPVpluginNeedsXEmbed:
      *((PRBool *)value) = PR_FALSE;
      break;
#endif
  }
  return NPERR_NO_ERROR;
}

static NPError /* expected by Safari on Darwin */
handleEvent(NPP instance, void *ev)
{
  inst = instance;
  return NPERR_NO_ERROR;
}

static HWND getParentWindow(HWND h, bool topMost = false)
{
  char szClass[80];
  char buffer[32];

  HWND p = h;
  HWND r = h;
  while(p != NULL)
  {
    GetClassName(p, &szClass[0], 79);

    r = p;
    if(lstrcmp(szClass, "Chrome_RenderWidgetHostHWND") == 0)
    {
      if(!topMost)
      {
        break;
      }
    }
    p = GetParent(p);
  }
  return r;
}

static NPError /* expected by Opera */
setWindow(NPP instance, NPWindow* pNPWindow)
{
  char buffer[34];
  inst = instance;

  if(pNPWindow && pNPWindow->window)
  {
    g_hwnd = (unsigned int)pNPWindow->window;

    p_hwnd = (unsigned int)getParentWindow((HWND)g_hwnd);
    b_hwnd = (unsigned int)getParentWindow((HWND)g_hwnd, true);

    g_thread = GetWindowThreadProcessId((HWND)b_hwnd, NULL);

  }

  return NPERR_NO_ERROR;
}




/* EXPORT */
#ifdef __cplusplus
extern "C" {
#endif

NPError OSCALL
NP_GetEntryPoints(NPPluginFuncs *nppfuncs)
{
#if defined(LOGMSG)
  logmsg("autolang: NP_GetEntryPoints\n");
#endif  
  nppfuncs->version       = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
  nppfuncs->newp          = create;
  nppfuncs->destroy       = destroy;
  nppfuncs->getvalue      = getValue;
  nppfuncs->event         = handleEvent;
  nppfuncs->setwindow     = setWindow;

  return NPERR_NO_ERROR;
}

#ifndef HIBYTE
#define HIBYTE(x) ((((uint32)(x)) & 0xff00) >> 8)
#endif

NPError OSCALL
NP_Initialize(NPNetscapeFuncs *npnf
#if defined(ANDROID)
              , NPPluginFuncs *nppfuncs, JNIEnv *env, jobject plugin_object
#elif !defined(_WINDOWS) && !defined(WEBKIT_DARWIN_SDK)
              , NPPluginFuncs *nppfuncs
#endif
              )
{
#if defined(LOGMSG)
  logmsg("autolang: NP_Initialize\n");
#endif  
  if(npnf == NULL)
    return NPERR_INVALID_FUNCTABLE_ERROR;

  if(HIBYTE(npnf->version) > NP_VERSION_MAJOR)
    return NPERR_INCOMPATIBLE_VERSION_ERROR;

  npnfuncs = npnf;
#if !defined(_WINDOWS) && !defined(WEBKIT_DARWIN_SDK)
  NP_GetEntryPoints(nppfuncs);
#endif
  return NPERR_NO_ERROR;
}

NPError
OSCALL NP_Shutdown()
{
#if defined(LOGMSG)
  logmsg("autolang: NP_Shutdown\n");
#endif  
  return NPERR_NO_ERROR;
}

#if defined(__BORLANDC__)
const
#endif
char *
OSCALL NP_GetMIMEDescription(void)
{
#if defined(LOGMSG)
  logmsg("autolang: NP_GetMIMEDescription\n");
#endif  
  return "application/x-auto-lang-keeper";
}

NPError OSCALL /* needs to be present for WebKit based browsers */
NP_GetValue(void *npp, NPPVariable variable, void *value)
{
  inst = (NPP)npp;
  return getValue((NPP)npp, variable, value);
}

#ifdef __cplusplus
}
#endif
